home *** CD-ROM | disk | FTP | other *** search
/ Linux Cubed Series 3: Developer Tools / Linux Cubed Series 3 - Developer Tools.iso / devel / lang / c / c_count-.0 / c_count- / c_count-7.0 / c_count.c < prev    next >
Encoding:
C/C++ Source or Header  |  1995-05-20  |  22.9 KB  |  1,016 lines

  1. #ifndef    NO_IDENT
  2. static    char    Id[] = "$Header: /home/tom/src/c_count/RCS/c_count.c,v 7.12 1995/05/20 23:43:24 tom Exp $";
  3. #endif
  4.  
  5. /*
  6.  * Title:    c_count.c
  7.  * Author:    T.E.Dickey
  8.  * Created:    04 Dec 1985
  9.  * Modified:
  10.  *        13 May 1995, split-off from td_lib.
  11.  *        28 Jul 1994, show totals even for empty file.
  12.  *        17 Jul 1994, renamed from 'lincnt', for clearer meaning.
  13.  *        23 Sep 1993, gcc warnings
  14.  *        16 Oct 1991, header-label for spreadsheet had "STATEMENTS" and
  15.  *                 "LINES" interchanged (fixed).  Also, converted to
  16.  *                 ANSI.
  17.  *        23 May 1991, apollo sr10.3 cpp complains about endif-tags
  18.  *        30 Aug 1990, history could be RCS or CMS (corrected message)
  19.  *        30 Aug 1990, added 'filter_history()' procedure, which filters
  20.  *                 out the history-comments generated by RCS or
  21.  *                 DEC/CMS from the total for "normal" comments.  Only
  22.  *                 "normal" comments are shown in the comment:code
  23.  *                 ratio.  If specific statistics are requested (e.g.,
  24.  *                 "-s") and the "-t" option is set, coerce "-p" as
  25.  *                 well.
  26.  *        29 Aug 1990, corrected format of 'show_flag()'
  27.  *        21 May 1990, corrected final (per-file) call on 'Summary()'
  28.  *        05 May 1990, rewrote, adding options to make this behave like
  29.  *                 "a.count".
  30.  *        14 May 1990, added "-t" (spreadsheet/table) option
  31.  *        10 May 1990, ported to VAX/VMS 5.3 (expand wildcards, added "-o"
  32.  *                 option).  Made usage-message more verbose
  33.  *        17 Oct 1989, assume "//" begins C++ inline comments
  34.  *        05 Oct 1989, lint (apollo SR10 "string" defs)
  35.  *        21 Jul 1989, permit use of "-" to indicate standard input.
  36.  *        01 Jun 1988, added token-length statistic
  37.  *        01 Jul 1987, test for junky files (i.e., non-ascii characters,
  38.  *                 nested comments, non-graphic characters in
  39.  *                 quotes), added '-v' verbose mode to show running
  40.  *                 totals & status per-file.  Added '-q' option to
  41.  *                 handle the case of unbalanced '"' in a macro
  42.  *                 (e.g., for a common printf-prefix).
  43.  *        07 Nov 1986, added tested for unbalanced quote, comment so we
  44.  *                 can get reasonable counts for ddn-driver, etc.
  45.  *                 Also fixed printf-formats for xenix-port.
  46.  *        23 Apr 1986, treat standard-input as a list of names, not code.
  47.  *        28 Jan 1985, make final 'exit()' with return code.
  48.  *
  49.  * Function:    Count lines and statements in one or more C program files,
  50.  *        giving statistics on percentage commented.
  51.  *
  52.  *        The file(s) are specified as command line arguments.  If no
  53.  *        arguments are given, then the file is read from standard input.
  54.  *
  55.  * TODO:    Make RCS-history filtering work with C++ comments.
  56.  *
  57.  * TODO:    Make RCS-history filtering smart enough to handle blank lines
  58.  *        in the history-comments (as opposed to blank lines between
  59.  *        successive revisions).
  60.  */
  61.  
  62. #include "system.h"
  63.  
  64. #include <stdio.h>
  65. #include <ctype.h>
  66.  
  67. #if HAVE_STDLIB_H
  68. #include <stdlib.h>
  69. #endif
  70.  
  71. #if HAVE_MALLOC_H
  72. #include <malloc.h>
  73. #endif
  74.  
  75. #if HAVE_STRING_H
  76. #include <string.h>
  77. #else
  78. #include <strings.h>
  79. #endif
  80.  
  81. #if HAVE_GETOPT_H
  82. #include <getopt.h>
  83. #else
  84. # if !DECLARED_GETOPT
  85. extern    int getopt ARGS((int argc, char **argv, char *opts));
  86. extern    int optind;
  87. extern    char *optarg;
  88. # endif
  89. #endif
  90.  
  91. #if !HAVE_STRCHR        /* normally in <string.h> */
  92. #define strchr index
  93. #endif
  94.  
  95. #ifndef EXIT_SUCCESS        /* normally in <stdlib.h> */
  96. #define EXIT_SUCCESS 0
  97. #define EXIT_FAILURE 0
  98. #endif
  99.  
  100. #define    OCTAL    3        /* # of octal digits permissible in escape */
  101.  
  102. #define PRINTF  (void)printf
  103. #define    DEBUG    if (debug) PRINTF
  104. #define    TOKEN(c)    ((c) == '_' || isalnum(c))
  105.  
  106. static    int    inFile  ARGS((void));
  107. static    int    Comment ARGS((int cpp));
  108. static    int    Escape  ARGS((void));
  109. static    int    String  ARGS((int mark));
  110.  
  111. static    FILE    *File;
  112. static    char    **quotvec;
  113.  
  114. static    int    literal;    /* true when permitting literal-tab */
  115.  
  116. typedef    struct    {
  117.         long    chars_total,    /* # of characters */
  118.             chars_blank,
  119.             chars_code,
  120.             chars_ignore,
  121.             chars_notes,
  122.             chars_rlogs,
  123.             chars_prepro;
  124.         long    lines_total,    /* # of lines */
  125.             lines_blank,
  126.             lines_code,
  127.             lines_inline,    /* in-line comments */
  128.             lines_notes,    /* all other comments */
  129.             lines_rlogs,    /* RCS history comments */
  130.             lines_prepro;
  131.         long    flags_unquo,
  132.             flags_uncmt,
  133.             flags_unasc;
  134.         long    stmts_total;
  135.         long    words_total,    /* # of tokens */
  136.             words_length;    /* total length of tokens */
  137.     } STATS;
  138.  
  139. static    STATS    All, One;    /* total, per-file stats */
  140.  
  141. static    long    old_unquo,
  142.         old_unasc,
  143.         old_uncmt;
  144.  
  145. enum    PSTATE    {code, comment, preprocessor};
  146. static    enum    PSTATE    pstate;
  147.  
  148. static    int    verbose    = FALSE,/* TRUE iff we echo file, as processed */
  149.         quotdef    = 0,    /* number of tokens we treat as '"' */
  150.         jargon    = FALSE,
  151.         per_file= FALSE,
  152.         debug    = FALSE,
  153.         opt_line,
  154.         opt_char,
  155.         opt_name,
  156.         opt_stat,
  157.         spreadsheet = FALSE,
  158.         cms_history,
  159.         files_total,
  160.         newsum;        /* TRUE iff we need a summary */
  161.  
  162. static    char    *comma    = ",";
  163. static    char    *dashes = "----------------";
  164.  
  165. /************************************************************************
  166.  *    local procedures                        *
  167.  ************************************************************************/
  168. #if PRINT_ROUNDS_DOWN
  169.      /*
  170.      * When I compared output on Apollo SR10.1 with SunOS 4.1.1, I found
  171.      * that sometimes the SunOS printf would round down (e.g., 0.05% would
  172.      * be rendered as 0.0%).  This code is used to round up so that I'd get
  173.      * the same numbers on different machines.
  174.      */
  175. static    double    RoundUp(
  176.     _ARG(double,    value),
  177.     _ARG(double,    parts)
  178.         )
  179.     _DCL(double,    value)
  180.     _DCL(double,    parts)
  181.     {
  182.         long    temp = value * parts * 10.0;
  183.         if ((temp % 10) == 5)    temp++;
  184.         return (temp / (parts * 10.0));
  185.     }
  186. #else
  187. #define    RoundUp(value,parts)    value
  188. #endif
  189.  
  190. static
  191. void    new_summary(NO_ARGS)
  192. {
  193.     if (!spreadsheet)
  194.         PRINTF ("\n");
  195. }
  196.  
  197. static
  198. void    per_cent(
  199.     _ARG(char *,    text),
  200.     _ARG(long,    num),
  201.     _ARG(long,    den)
  202.         )
  203.     _DCL(char *,    text)
  204.     _DCL(long,    num)
  205.     _DCL(long,    den)
  206. {
  207.     double    value;
  208.     if (spreadsheet) {
  209.         PRINTF("%ld%s", num, comma);
  210.         return;
  211.     }
  212.     if (num == 0 || den == 0)
  213.         value = 0.0;
  214.     else
  215.         value = RoundUp((num * 100.0) / den, 10.0);
  216.     PRINTF("%6ld\t%-24s%6.1f %%\n", num, text, value);
  217. }
  218.  
  219. static
  220. void    show_a_flag(
  221.     _ARG(char *,    text),
  222.     _ARG(long,    flag)
  223.         )
  224.     _DCL(char *,    text)
  225.     _DCL(long,    flag)
  226. {
  227.     if (spreadsheet)
  228.         PRINTF("%ld%s", flag, comma);
  229.     else if (flag)
  230.         PRINTF("%6ld\t%s\n", flag, text);
  231. }
  232.  
  233. static
  234. void    ratio(
  235.     _ARG(char *,    text),
  236.     _ARG(long,    num),
  237.     _ARG(long,    den)
  238.         )
  239.     _DCL(char *,    text)
  240.     _DCL(long,    num)
  241.     _DCL(long,    den)
  242. {
  243.     if (den == 0) den = 1;
  244.     if (spreadsheet) {
  245.         PRINTF("%.2f%s", (float)(num) / den, comma);
  246.         return;
  247.     }
  248.     PRINTF("%6.2f\tratio of %s\n", (float)(num) / den, text);
  249. }
  250.  
  251. static
  252. void    summarize_lines(
  253.     _ARG(STATS *,    p))
  254.     _DCL(STATS *,    p)
  255. {
  256.     auto    long    den = p->lines_total;
  257.  
  258.     new_summary();
  259.     per_cent("lines had comments",
  260.         p->lines_notes + p->lines_inline, den);
  261.     if (p->lines_rlogs || spreadsheet)
  262.     per_cent("lines had history",      p->lines_rlogs,    den);
  263.     per_cent("comments are inline",      p->lines_inline,    -den);
  264.     per_cent("lines were blank",      p->lines_blank,    den);
  265.     per_cent("lines for preprocessor",p->lines_prepro,    den);
  266.     per_cent("lines containing code", p->lines_code,    den);
  267.     per_cent(jargon ?
  268.          "total lines (PSS)" :
  269.          "total lines",
  270.         p->lines_notes + p->lines_rlogs + p->lines_blank +
  271.         p->lines_prepro + p->lines_code,
  272.         den);
  273. }
  274.  
  275. static
  276. void    summarize_chars(
  277.     _ARG(STATS *,    p))
  278.     _DCL(STATS *,    p)
  279. {
  280.     auto    long    den = p->chars_total;
  281.  
  282.     new_summary();
  283.     per_cent("comment-chars",      p->chars_notes,    den);
  284.     if (p->chars_rlogs || spreadsheet)
  285.     per_cent("history-chars",      p->chars_rlogs,    den);
  286.     per_cent("nontext-comment-chars", p->chars_ignore,    den);
  287.     per_cent("whitespace-chars",      p->chars_blank,    den);
  288.     per_cent("preprocessor-chars",      p->chars_prepro,    den);
  289.     per_cent("statement-chars",      p->chars_code,    den);
  290.     per_cent("total characters",
  291.         p->chars_blank +
  292.         p->chars_notes + p->chars_rlogs + p->chars_ignore +
  293.         p->chars_prepro + p->chars_code,
  294.         den);
  295. }
  296.  
  297. static
  298. void    summarize_names(
  299.     _ARG(STATS *,    p))
  300.     _DCL(STATS *,    p)
  301. {
  302.     new_summary();
  303.     if (spreadsheet) {
  304.         PRINTF("%ld%s%ld%s",
  305.             p->words_total, comma,
  306.             p->words_length, comma);
  307.         return;
  308.     }
  309.     if (p->words_total)
  310.         PRINTF("%6ld\ttokens, average length %.2f\n",
  311.             p->words_total,
  312.             (1.0 * p->words_length) / p->words_total);
  313. }
  314.  
  315. static
  316. void    summarize_stats(
  317.     _ARG(STATS *,    p))
  318.     _DCL(STATS *,    p)
  319. {
  320.     new_summary();
  321.     ratio("comment:code", p->chars_notes, p->chars_prepro + p->chars_code);
  322.     show_a_flag("?:illegal characters found",    p->flags_unasc);
  323.     show_a_flag("\":lines with unterminated quotes",p->flags_unquo);
  324.     show_a_flag("*:unterminated/nested comments",    p->flags_uncmt);
  325. }
  326.  
  327. static
  328. void    show_totals(
  329.     _ARG(STATS *,    p))
  330.     _DCL(STATS *,    p)
  331. {
  332.     if (opt_line)    summarize_lines(p);
  333.     if (opt_char)    summarize_chars(p);
  334.     if (opt_name)    summarize_names(p);
  335.     if (opt_stat)    summarize_stats(p);
  336. }
  337.  
  338. static
  339. void    summarize(
  340.     _ARG(STATS *,    p),
  341.     _ARG(int,    mark)
  342.         )
  343.     _DCL(STATS *,    p)
  344.     _DCL(int,    mark)
  345. {
  346.     newsum = FALSE;
  347.     if (spreadsheet) {
  348.         PRINTF ("%ld%s%ld%s",
  349.             p->lines_total,    comma,
  350.             p->stmts_total,    comma);
  351.     } else {
  352.         PRINTF ("%6ld %5ld%c%c%c%c",
  353.             p->lines_total,
  354.             p->stmts_total,
  355.             (p->flags_unasc != old_unasc ? '?' : ' '),
  356.             (p->flags_unquo != old_unquo ? '"' : ' '),
  357.             (p->flags_uncmt != old_uncmt ? '*' : ' '),
  358.             (mark  ? '|' : ' '));
  359.     }
  360.     old_unasc = p->flags_unasc;
  361.     old_uncmt = p->flags_uncmt;
  362.     old_unquo = p->flags_unquo;
  363. }
  364.  
  365. static
  366. void    Summary(
  367.     _ARG(int,    mark))
  368.     _DCL(int,    mark)
  369. {
  370.     if (newsum)
  371.         summarize(&One,mark);
  372. }
  373.  
  374. #define    ADD(m)    All.m += One.m; One.m = 0
  375.  
  376. static
  377. void    add_totals (NO_ARGS)
  378. {
  379.     ADD(chars_total);
  380.     ADD(chars_blank);
  381.     ADD(chars_code);
  382.     ADD(chars_ignore);
  383.     ADD(chars_notes);
  384.     ADD(chars_rlogs);
  385.     ADD(chars_prepro);
  386.  
  387.     ADD(lines_total);
  388.     ADD(lines_blank);
  389.     ADD(lines_code);
  390.     ADD(lines_inline);
  391.     ADD(lines_notes);
  392.     ADD(lines_rlogs);
  393.     ADD(lines_prepro);
  394.  
  395.     ADD(flags_unquo);
  396.     ADD(flags_uncmt);
  397.     ADD(flags_unasc);
  398.  
  399.     ADD(stmts_total);
  400.  
  401.     ADD(words_total);
  402.     ADD(words_length);
  403.     files_total++;
  404. }
  405.  
  406. /*
  407.  * If '-q' option is in effect, append to the token-buffer until a token is
  408.  * complete.  Test the completed token to see if it matches any of the strings
  409.  * we have equated to '"'.  If so, process a string from that point.  Note that
  410.  * there are two special cases which fortuitously work out:
  411.  *    (a) in the "#define" statement, the whitespace-character immediately
  412.  *        after the token is ignored.
  413.  *    (b) in the usage of the token, the whitespace-character following the
  414.  *        token is ignored.
  415.  * To provide for the special case of one define providing an alternate name
  416.  * for another, we do the lookup/string processing only if a quote-macro could
  417.  * be expanded there, e.g., if it is followed by a space or tab.
  418.  */
  419. static
  420. int    Token(
  421.     _ARG(int,    c))
  422.     _DCL(int,    c)
  423. {
  424.     static    char    bfr[80];
  425.     static    int    len = 0;
  426.     register int    j = 0;
  427.  
  428.     if (quotdef) {
  429.         One.words_total++;
  430.         while (TOKEN(c)) {
  431.             One.words_length++;
  432.             if (len < sizeof(bfr)-1) bfr[len++] = c;
  433.             c = inFile();
  434.             j++;
  435.         }
  436.         if (len) {
  437.             if (c == ' ' || c == '\t' || c == '(') {
  438.                 bfr[len] = EOS;
  439.                 for (j = 0; j < quotdef; j++) {
  440.                     if (!strcmp(quotvec[j],bfr)) {
  441.                         c = String('"');
  442.                         DEBUG("**%c**",c);
  443.                         break;
  444.                     }
  445.                 }
  446.                 DEBUG("%s\n", bfr);
  447.             }
  448.             len = 0;
  449.         } else if (!j)
  450.             c = inFile();
  451.         bfr[len] = 0;
  452.     } else {
  453.         if (TOKEN(c)) {
  454.             One.words_total++;
  455.             DEBUG("'");
  456.             do {
  457.                 One.words_length++;
  458.                 DEBUG("%c", c);
  459.                 c = inFile();
  460.             } while (TOKEN(c));
  461.             DEBUG("'\n");
  462.         } else    /* punctuation */
  463.             c = inFile();
  464.     }
  465.     return(c);
  466. }
  467.  
  468. /*
  469.  * Process a single file:
  470.  */
  471. static
  472. void    doFile (
  473.     _ARG(char *,    name))
  474.     _DCL(char *,    name)
  475. {
  476.     register int c;
  477.  
  478. #if !SYS_UNIX
  479.     if (has_wildcard(name)) {        /* expand wildcards? */
  480.         auto    char    expanded[BUFSIZ];
  481.         auto    int    count    = 0;
  482.         (void) strcpy(expanded, name);
  483.         while (expand_wildcard(expanded, !count++))
  484.             doFile(expanded);
  485.         return;
  486.     }
  487.     /* trim trailing blanks */
  488.     for (c = strlen(name); c > 0; c--) {
  489.         if (isspace(name[--c]))
  490.             name[c] = EOS;
  491.         else
  492.             break;
  493.     }
  494. #endif
  495.  
  496.     pstate = code;
  497.     if (!strcmp(name, "-"))
  498.         File = stdin;
  499.     else if (!(File = fopen (name, "r")))
  500.         return;
  501.  
  502.     newsum = TRUE;
  503.     cms_history = FALSE;
  504.     c = inFile ();
  505.  
  506.     while (c != EOF) {
  507.         switch (c) {
  508.         case '/':
  509.             c = Token(EOS);
  510.             if (c == '*')
  511.                 c = Comment(FALSE);
  512.             else if (c == '/')
  513.                 c = Comment(TRUE);
  514.             break;
  515.         case '"':
  516.         case '\'':
  517.             c = String(c);
  518.             break;
  519.         case ';':
  520.             One.stmts_total++;
  521.         default:
  522.             c = Token(c);
  523.         }
  524.     }
  525.     (void)Token(EOS);
  526.     (void)fclose(File);
  527.  
  528.     if (per_file && spreadsheet) {
  529.         show_totals(&One);
  530.     } else if (!per_file)
  531.         summarize(&One,TRUE);
  532.     PRINTF("%s\n", name);
  533.     if (per_file && !spreadsheet) {
  534.         show_totals(&One);
  535.         PRINTF("\n");
  536.     }
  537.     add_totals();
  538. }
  539.  
  540. /*
  541.  * Scan over a quoted string.  Except for the special (automatic) case of
  542.  * "@(#)"-sccs strings, flag all nonprinting characters which are found in
  543.  * the string in 'unasc'.  Also, flag places where we have a newline in a
  544.  * string (perhaps because the leading quote was in a macro!).
  545.  */
  546. static
  547. int    String (
  548.     _ARG(int, mark))
  549.     _DCL(int, mark)
  550. {
  551.     register int c = inFile();
  552.     char    *p = "@(#)";        /* permit literal tab here only! */
  553.  
  554.     literal = TRUE;
  555.     while (c != EOF) {
  556.         if (p != 0) {
  557.             if (*p == '\0') {
  558.                 if (!isprint(c) && (c != '\t'))
  559.                     One.flags_unasc++;
  560.             } else if (c != *p++)
  561.                 p = 0;
  562.         }
  563.         if ((p == 0) && !isprint(c))
  564.             One.flags_unasc++; /* this may duplicate 'unquo'! */
  565.         if (c == '\n') {    /* this is legal, but not likely */
  566.             One.flags_unquo++; /* ...assume balance is in macro */
  567.             return (inFile());
  568.         } else if (c == mark) {
  569.             literal = FALSE;
  570.             return (inFile());
  571.         }
  572.         else if (c == '\\')    c = Escape();
  573.         else            c = inFile();
  574.     }
  575.     literal = FALSE;
  576.     return (c);
  577. }
  578.  
  579. /*
  580.  * Scan over an '\' escape sequence, returning the first character after it.
  581.  * If we start with an octal digit, we may read up to OCTAL of these in a row.
  582.  */
  583. static
  584. int    Escape (NO_ARGS)
  585. {
  586.     register int c = inFile(),
  587.         digits = 0;
  588.  
  589.     if (c != EOF) {
  590.         while (c >= '0' && c <= '7' && digits < OCTAL) {
  591.             digits++;
  592.             if ((c = inFile()) == EOF)    return (c);
  593.         }
  594.         if (!digits) c = inFile();
  595.     }
  596.     return (c);
  597. }
  598.  
  599. /*
  600.  * Identify comments which we disregard because they were generated by the
  601.  * RCS history mechanism.
  602.  */
  603. static
  604. void    Disregard(
  605.     _ARG(char *,    lo),
  606.     _ARG(char *,    hi)
  607.         )
  608.     _DCL(char *,    lo)
  609.     _DCL(char *,    hi)
  610. {
  611.     while (lo <= hi) {
  612.         if (isalnum(*lo)) {
  613.             One.chars_notes -= 1;
  614.             One.chars_rlogs += 1;
  615.         }
  616.         lo++;
  617.     }
  618.     One.lines_notes -= 1;
  619.     One.lines_rlogs += 1;
  620. }
  621.  
  622. /*
  623.  * Compare strings ignoring blanks (TD_LIB)
  624.  */
  625. #define    SKIP(p)    while (isspace(*p))    p++;
  626.  
  627. static
  628. int    strbcmp(
  629.     _ARG(register char *,    a),
  630.     _ARG(register char *,    b)
  631.         )
  632.     _DCL(register char *,    a)
  633.     _DCL(register char *,    b)
  634. {
  635.     register int    cmp;
  636.  
  637.     while (*a && *b) {
  638.         if (isspace(*a) && isspace(*b)) {
  639.             SKIP(a);
  640.             SKIP(b);
  641.         } else if ((cmp = (*a++ - *b++)) != EOS)
  642.             return (cmp);
  643.     }
  644.     SKIP(a);
  645.     SKIP(b);
  646.     return (*a - *b);
  647. }
  648.  
  649. /*
  650.  * Read characters within a comment, keeping track to find RCS or DEC/CMS
  651.  * history comments.
  652.  *
  653.  * RCS history comments consist of an RCS identifier "Log", followed by zero
  654.  * or more groups of lines beginning with the keyword "Revision". Each line in
  655.  * the group is prefixed by the same string (the RCS "comment" prefix).  The
  656.  * groups are separated by a line containing only the comment-prefix.
  657.  *
  658.  * DEC/CMS history comments consist of one or more comment lines between
  659.  * comments containing the string shown below.  These comments (due to the
  660.  * nature of CMS's history substitution) must be self-contained on a line,
  661.  * unlike the RCS history.
  662.  */
  663. static
  664. int    filter_history(
  665.     _ARG(int,    first))
  666.     _DCL(int,    first)
  667. {
  668.     enum    HSTATE    {unknown, cms, rlog, revision, contents};
  669.     static    char    *CMS_    = "DEC/CMS REPLACEMENT HISTORY,";
  670.     static    enum    HSTATE    hstate    = unknown;
  671.     static    char    buffer[BUFSIZ];
  672.     static    char    prefix[BUFSIZ];
  673.     static    size_t    len;
  674.  
  675.     auto    char    *s, *d, *t;
  676.  
  677.     register int    c = inFile();
  678.  
  679.     if (first) {
  680.         buffer[len = 0]    = EOS;
  681.         prefix[0]    = EOS;
  682.         hstate        = cms_history ? cms : unknown;
  683.     } else if (len < sizeof(buffer)) {
  684.         buffer[len++]    = c;
  685.         buffer[len]    = EOS;
  686.     }
  687.     if (c == '\n') {
  688.         /* try to find CMS bracketing comment */
  689.         len = strlen(CMS_);
  690.         for (s = buffer; *s; s++) {
  691.             if (!strncmp(s, CMS_, len)) {
  692.                 cms_history = !cms_history;
  693.                 hstate = cms;
  694.                 break;
  695.             }
  696.         }
  697.  
  698.         /* ignore all comments within DEC/CMS bracketing */
  699.         if (hstate == cms) {
  700.             Disregard(buffer, buffer + strlen(buffer));
  701.  
  702.         /* try to find RCS identifier "Log" */
  703.         } else if (hstate == unknown) {
  704.             s = buffer;
  705.             while ((d = strchr(s, '$')) != NULL) {
  706.                 s = d + 1;
  707.                 if (!strncmp(d, "$Log", 4)
  708.                 &&  (t = strchr(s, '$')) != 0) {
  709.                     hstate = rlog;
  710.                     Disregard(d,t);
  711.                     break;
  712.                 }
  713.             }
  714.         /* try to find "Revision" after comment-prefix */
  715.         } else if (hstate == rlog) {
  716.             s = buffer;
  717.             while ((d = strchr(s, 'R')) != NULL) {
  718.                 s = d + 1;
  719.                 if (!strncmp(d, "Revision", 8)) {
  720.                     size_t len2 = (size_t)(d-buffer);
  721.                     strcpy(prefix, buffer)[len2] = EOS;
  722.                     hstate = revision;
  723.                     s += strlen(s);
  724.                     Disregard(d,s-2);
  725.                 }
  726.             }
  727.         } else if (hstate == revision
  728.             || hstate == contents) {
  729.             if (!strncmp(buffer, prefix, (size_t)strlen(prefix))) {
  730.                 d = buffer + strlen(prefix);
  731.                 s = d + strlen(d);
  732.                 hstate = (*d == '\n') ? rlog : contents;
  733.                 Disregard(d,s-2);
  734.             } else if (!strbcmp(buffer, prefix)) {
  735.                 hstate = rlog;    /* assume user trimmed spaces */
  736.             } else
  737.                 hstate = unknown;
  738.         }
  739.         buffer[len = 0] = EOS;
  740.     }
  741.     return (c);
  742. }
  743.  
  744. /*
  745.  * Entered immediately after reading '/','*', scan over a comment, returning
  746.  * the first character after the comment.  Flags both unterminated comments
  747.  * and nested comments with 'uncmt'.
  748.  */
  749. static
  750. int    Comment (
  751.     _ARG(int,    c_plus_plus))
  752.     _DCL(int,    c_plus_plus)
  753. {
  754.     register int    c;
  755.     auto    int    d = 0;
  756.     auto    enum    PSTATE    save_st = pstate;
  757.  
  758.     if (pstate == code)
  759.         One.chars_code -= 2;
  760.     else
  761.         One.chars_prepro -= 2;
  762.     One.chars_ignore += 2;        /* ignore the comment-delimiter */
  763.  
  764.     pstate = comment;
  765.     c = filter_history(TRUE);
  766.     while (c != EOF) {
  767.         if (c_plus_plus) {
  768.             if (c == '\n') {
  769.                 pstate = save_st;
  770.                 return(c);
  771.             }
  772.             c = filter_history(FALSE);
  773.         } else {
  774.             if (c == '*') {
  775.                 c = filter_history(FALSE);
  776.                 if (c == '/') {
  777.                     pstate = save_st;
  778.                     return (inFile());
  779.                 }
  780.             } else {
  781.                 c = filter_history(FALSE);
  782.                 if (c == '*' && d == '/')
  783.                     One.flags_uncmt++;
  784.             }
  785.         }
  786.         d = c;
  787.     }
  788.     One.flags_uncmt++;
  789.     return (c);            /* Unterminated comment! */
  790. }
  791.  
  792. /*
  793.  * Return the next character from the current file, using the global file
  794.  * pointer.
  795.  */
  796. static
  797. int    inFile (NO_ARGS)
  798. {
  799.     static    int    last_c;
  800.     static    int    is_blank;    /* true til we get nonblank on line */
  801.     static    int    had_note,
  802.             had_code;
  803. register int c = fgetc(File);
  804.  
  805.     if (One.chars_total == 0) {
  806.         old_unquo =
  807.         old_unasc =
  808.         old_uncmt = 0;
  809.         had_note  =
  810.         had_code  = FALSE;
  811.         last_c    = EOS;
  812.         is_blank  = TRUE;
  813.     }
  814.  
  815.     if (feof(File) || ferror(File))
  816.         c = EOF;
  817.     else
  818.         c &= 0xff;        /* protect against sign-extension bug */
  819.     if (c != EOF) {
  820.         if (verbose && (!One.chars_total || last_c == '\n'))
  821.             Summary(TRUE);
  822.         newsum = TRUE;
  823.         if (!isascii(c) || (!isprint(c) && !isspace(c))) {
  824.             c = '?';        /* protect/flag this */
  825.             One.flags_unasc++;
  826.         }
  827.         if (verbose) {
  828.             c = putchar(c);
  829.         }
  830.         One.chars_total++;
  831.         if (c == '#' && is_blank)
  832.             pstate = preprocessor;
  833.         else if (c == '\n') {
  834.             One.lines_total++;
  835.             if (is_blank)
  836.                 One.lines_blank++;
  837.             else {
  838.                 if (pstate == preprocessor) {
  839.                     One.lines_prepro++;
  840.                     if (had_note)
  841.                         One.lines_inline++;
  842.                 } else if (had_code) {
  843.                     One.lines_code++;
  844.                     if (had_note)
  845.                         One.lines_inline++;
  846.                 } else if (had_note)
  847.                     One.lines_notes++;
  848.                 had_code =
  849.                 had_note = FALSE;
  850.             }
  851.             is_blank = TRUE;
  852.             if (pstate == preprocessor && last_c != '\\')
  853.                 pstate = code;
  854.         }
  855.         if (isspace(c)) {
  856.             if (literal)
  857.                 One.chars_code++;
  858.             else
  859.                 One.chars_blank++;
  860.         } else {
  861.             is_blank = FALSE;
  862.             switch (pstate) {
  863.             case comment:
  864.                 had_note = TRUE;
  865.                 if (isalnum(c))
  866.                     One.chars_notes++;
  867.                 else
  868.                     One.chars_ignore++;
  869.                 break;
  870.             case preprocessor:
  871.                 One.chars_prepro++;
  872.                 break;
  873.             default:
  874.                 had_code = TRUE;
  875.                 One.chars_code++;
  876.             }
  877.         }
  878.     }
  879.     last_c = c;
  880.     return (c);
  881. }
  882.  
  883. static
  884. void    usage(NO_ARGS)
  885. {
  886.     static    char    *tbl[] = {
  887.  "Usage: c_count [options] [files]"
  888. ,""
  889. ,"If no files are specified as arguments, a list of filenames is read from the"
  890. ,"standard input.  The special name \"-\" denotes a file which is read from the"
  891. ,"standard input."
  892. ,""
  893. ,"Options:"
  894. ," -c        character-statistics"
  895. ," -d        debug (shows tokens as they are parsed)"
  896. ," -i        identifier-statistics"
  897. ," -j        annotate summary in technical format"
  898. ," -l        line-statistics"
  899. ," -o file   specify alternative output-file"
  900. ," -p        per-file statistics"
  901. ," -q DEFINE tells c_count that the given name is an unbalanced quote"
  902. ," -s        specialized statistics"
  903. ," -t        generate output for spreadsheet"
  904. ," -v        verbose (shows lines as they are counted)"
  905.     };
  906.     register int    j;
  907.     for (j = 0; j < sizeof(tbl)/sizeof(tbl[0]); j++)
  908.         (void)fprintf(stderr, "%s\n", tbl[j]);
  909.     (void)exit(EXIT_FAILURE);
  910. }
  911.  
  912. /************************************************************************
  913.  *    main procedure                            *
  914.  ************************************************************************/
  915.  
  916. /*ARGSUSED*/
  917. int    main (
  918.     _ARG(int,    argc),
  919.     _ARG(char **,    argv)
  920.         )
  921.     _DCL(int,    argc)
  922.     _DCL(char **,    argv)
  923. {
  924.     register int j;
  925.     auto    char    name[BUFSIZ];
  926.     auto    int    opt_all = -1;
  927.  
  928.     quotvec = typeCalloc(char *, (size_t)argc);
  929.     while ((j = getopt(argc,argv,"cdijlo:pq:stv")) != EOF) switch(j) {
  930.     case 'l':    opt_all = FALSE; opt_line = TRUE; break;
  931.     case 'c':    opt_all = FALSE; opt_char = TRUE; break;
  932.     case 'i':    opt_all = FALSE; opt_name = TRUE; break;
  933.     case 's':    opt_all = FALSE; opt_stat = TRUE; break;
  934.  
  935.     case 'd':    debug    = TRUE;    break;
  936.     case 'j':    jargon    = TRUE;    break;
  937.     case 'p':    per_file= TRUE;    break;
  938.     case 'v':    verbose = TRUE;    break;
  939.     case 'o':    if (!freopen(optarg, "w", stdout))
  940.                 usage();
  941.             break;
  942.     case 'q':    quotvec[quotdef++] = optarg;
  943.             break;
  944.     case 't':    spreadsheet = TRUE;    break;
  945.     default:    usage();
  946.     }
  947.  
  948.     if (opt_all == -1)
  949.         opt_line = opt_char = opt_name = opt_stat = TRUE;
  950.     else if (spreadsheet)
  951.         per_file = TRUE;
  952.  
  953.     if (spreadsheet) {
  954.         if (per_file) {
  955.             if (opt_line)
  956.                 PRINTF("%s%s%s%s%s%s%s%s%s%s%s%s%s%s",
  957.                     "L-COMMENT",    comma,
  958.                     "L-HISTORY",    comma,
  959.                     "L-INLINE",    comma,
  960.                     "L-BLANK",    comma,
  961.                     "L-CPP",    comma,
  962.                     "L-CODE",    comma,
  963.                     "L-TOTAL",    comma);
  964.             if (opt_char)
  965.                 PRINTF("%s%s%s%s%s%s%s%s%s%s%s%s%s%s",
  966.                     "C-COMMENT",    comma,
  967.                     "C-HISTORY",    comma,
  968.                     "C-IGNORE",    comma,
  969.                     "C-BLANK",    comma,
  970.                     "C-CPP",    comma,
  971.                     "C-CODE",    comma,
  972.                     "C-TOTAL",    comma);
  973.             if (opt_name)
  974.                 PRINTF("%s%s%s%s",
  975.                     "W-TOTAL",    comma,
  976.                     "W-LENGTH",    comma);
  977.             if (opt_stat)
  978.                 PRINTF("%s%s%s%s%s%s%s%s",
  979.                     "CODE:COMMENT",    comma,
  980.                     "ILLEGAL-CHARS", comma,
  981.                     "ILLEGAL-QUOTES", comma,
  982.                     "ILLEGAL-COMMENTS", comma);
  983.  
  984.         } else
  985.             PRINTF("LINES%sSTATEMENTS%s", comma, comma);
  986.         PRINTF("FILENAME\n");
  987.     }
  988.  
  989.     if (optind < argc) {
  990.         for (j = optind; j < argc; j++)
  991.             doFile (argv[j]);
  992.     } else {
  993.         while (gets(name)) {
  994.             doFile (name);
  995.         }
  996.     }
  997.  
  998.     if (!spreadsheet && files_total) {
  999.         if (!per_file) {
  1000.             PRINTF ("%s\n", dashes);
  1001.             summarize(&All,FALSE);
  1002.             PRINTF("%s\n",
  1003.                 jargon ?
  1004.                 "physical source statements/logical source statements" :
  1005.                 "total lines/statements");
  1006.         } else {
  1007.             PRINTF("Grand total\n");
  1008.             PRINTF ("%s\n", dashes);
  1009.         }
  1010.         show_totals(&All);
  1011.         PRINTF("\n");
  1012.     }
  1013.     (void)exit(EXIT_SUCCESS);
  1014.     /*NOTREACHED*/
  1015. }
  1016.